package actor

import (
	"fmt"
	"log"
	"time"

	aactor "github.com/asynkron/protoactor-go/actor"

	kadefine "kiwi/actor/define"
	kawork "kiwi/actor/work"
	kdefine "kiwi/define"
)

func Init(rootName string, deciderFunc kadefine.DeciderFunc, options ...kadefine.ConfigOption) {
	gSystem = aactor.NewActorSystem(options...)

	if deciderFunc == nil {
		deciderFunc = ResumeDecider
	}

	props := aactor.PropsFromFunc(receive,
		aactor.WithSupervisor(aactor.NewOneForOneStrategy(0, 0, deciderFunc)))

	gRootStartedWg.Add(1)

	if rootName == "" {
		rootName = "root"
	}

	var err error
	gRootPID, err = RootContext().SpawnNamed(props, rootName)
	if err != nil {
		log.Panicf("spawn root fail, %v", err)
	}

	gRootStartedWg.Wait()
}

func Final() {
	Poison(gRootPID)
	gRootStoppedWg.Wait()
}

func RootContext() *kadefine.RootContext {
	return gSystem.Root
}

func RootPID() *kadefine.PID {
	return gRootPID
}

func Spawn(iActor kdefine.IActor, ctx kadefine.Context) (*kadefine.PID, error) {
	if iActor == nil {
		return nil, fmt.Errorf("iActor is nil")
	}

	a := create(iActor)

	a.startedWaitGroup.Add(1)

	var propsOptions []kadefine.PropsOption

	if a.MailBoxSize() > 0 {
		propsOptions = append(propsOptions, aactor.WithMailbox(aactor.Bounded(a.MailBoxSize())))
	} else {
		propsOptions = append(propsOptions, aactor.WithMailbox(aactor.Unbounded()))
	}

	if a.DeciderFunc() != nil {
		propsOptions = append(propsOptions, aactor.WithSupervisor(aactor.NewOneForOneStrategy(0, 0, a.DeciderFunc())))
	}

	props := aactor.PropsFromFunc(a.receive, propsOptions...)

	var pid *kadefine.PID
	var err error

	if ctx != nil {
		if a.id != "" {
			pid, err = ctx.SpawnNamed(props, a.id)
			if err != nil {
				return nil, fmt.Errorf("ctx.SpawnNamed fail, %w", err)
			}
		} else {
			pid = ctx.Spawn(props)
		}
	} else {
		if a.FromRoot() {
			err = kawork.Dispatch(RootContext(), RootPID(), 0, kawork.DispatchFunc(func(ctx kadefine.Context) error {
				if a.id != "" {
					pid, err = ctx.SpawnNamed(props, a.id)
					if err != nil {
						return fmt.Errorf("gRootPID ctx.SpawnNamed fail, %w", err)
					}
				} else {
					pid = ctx.Spawn(props)
				}
				return nil
			}))
		} else {
			if a.id != "" {
				pid, err = RootContext().SpawnNamed(props, a.id)
				if err != nil {
					return nil, fmt.Errorf("RootContext().SpawnNamed fail, %w", err)
				}
			} else {
				pid = ctx.Spawn(props)
			}
		}
	}
	if err != nil {
		return nil, err
	}

	a.startedWaitGroup.Wait()

	return pid, nil
}

func Stop(pid *kadefine.PID) {
	RootContext().Stop(pid)
}

func Poison(pid *kadefine.PID) {
	RootContext().Poison(pid)
}

func Send(pid *kadefine.PID, message any) {
	RootContext().Send(pid, message)
}

func RequestFuture(pid *kadefine.PID, message any, timeout time.Duration) *kadefine.Future {
	return RootContext().RequestFuture(pid, message, timeout)
}
